Skip to main content

VOIP/Video Consultations

VoipConfig Object

For chat consultations, the consultation data will include the ChatConfig object This config data will be used to initiate and handle the call between the user and the doctor

class VoipConfig {
int? id;
int? consultationId;
String? apiKey;
String? callId;
String? token;

VoipConfig({
this.id,
this.consultationId,
this.apiKey,
this.callId,
this.token,
});
}

For VOIP consultation the config data will be in voipConfig and for VIDEO will be in videoConfig but it will be the same data structure so will be using one interface for both VoipConfig

VOIP and Video consultations are rendered and handled the same way, the difference is ro enable the video on Video consultations and disable it for VOIP consultations.

VOIP/Video Example

Check the below code example of using our ready-to-use components to handle VOIP/Video consultations:

class VideoPage extends StatefulWidget {
final String apikey;
final String callID;
final String token;
final int consultationID;
final bool voip;
const VideoPage({Key? key , required this.consultationID, required this.apikey, required this.callID, required this.token , required this.voip}) : super(key: key);

@override
State<VideoPage> createState() => _VideoPageState();
}

class _VideoPageState extends State<VideoPage> with WidgetsBindingObserver {
late VideoConfig _config;
VideoController? _controller;
bool isFullScreen = true;

bool audioEnabled = true;
bool videoEnabled = true;

@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
initPlatformState();
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
_controller?.resume();
break;
case AppLifecycleState.paused:
_controller?.pause();
break;
default:
}
}

// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
_config = VideoConfig(
apiKey: widget.apikey,
sessionId:widget.callID,
token: widget.token,
);

_controller = VideoController();

_controller?.stateUpdateCallBack = (ConnectionStateCallback stateUpdate) async {
if (stateUpdate.state.toString() == "ConnectionState.loggedIn" && widget.voip){
_controller?.toggleVideo(false);
setState(() => videoEnabled = false);
}
};
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.camera,
Permission.microphone,
].request();
final isGranted =
statuses[Permission.camera] == PermissionStatus.granted &&
statuses[Permission.microphone] == PermissionStatus.granted;
if (isGranted) {
_controller?.initSession(_config);
} else {
debugPrint(
"Camera or Microphone permission or both denied by the user!");
}
});
}

void videoControl (isEnabled){
_controller?.toggleVideo(!isEnabled);
setState(() => videoEnabled = !isEnabled);
}
void endSession () async {
try {
await ApiService().cancelConsultation(widget.consultationID);
} catch (e) {
debugPrint("endSession error $e");
}
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const Consultation()),
(Route<dynamic> route) => false,
);
}
void onCameraButtonTap (){
_controller?.toggleCamera();
}
void onMicButtonTap (isEnabled){
_controller?.toggleAudio(!isEnabled);
setState(() => audioEnabled = !isEnabled);
}
void onFullScreenButtonTap (){
setState(() => isFullScreen = !isFullScreen);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),

),
body: Column(
children: [
Flexible(
child: SizedBox(
height: isFullScreen
? MediaQuery.of(context).size.height
: MediaQuery.of(context).size.height * 0.5,
child: VideoView(
controller: _controller ?? VideoController(),
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding:const EdgeInsets.all(10.0),
child: Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing:10,
children:widget.voip ? [
ElevatedButton(
onPressed: endSession,
style: ButtonStyle(
shape: MaterialStateProperty.all<CircleBorder>(
const CircleBorder()),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.all(10.0),
),
elevation: MaterialStateProperty.all<double>(8.0),
backgroundColor:
MaterialStateProperty.all<Color>(Colors.red),
),
child: const Icon(Icons.call_end),
),
ElevatedButton(
onPressed: () => onMicButtonTap.call(audioEnabled),
style: ButtonStyle(
shape: MaterialStateProperty.all<CircleBorder>(
const CircleBorder()),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.all(10.0),
),
elevation: MaterialStateProperty.all<double>(8.0),
),
child:audioEnabled
? const Icon(Icons.mic)
: const Icon(Icons.mic_off),
),
] : [
ElevatedButton(
onPressed: endSession,
style: ButtonStyle(
shape: MaterialStateProperty.all<CircleBorder>(
const CircleBorder()),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.all(10.0),
),
elevation: MaterialStateProperty.all<double>(8.0),
backgroundColor:
MaterialStateProperty.all<Color>(Colors.red),
),
child: const Icon(Icons.call_end),
),
ElevatedButton(
onPressed: onCameraButtonTap,
style: ButtonStyle(
shape: MaterialStateProperty.all<CircleBorder>(
const CircleBorder()),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.all(10.0),
),
elevation: MaterialStateProperty.all<double>(8.0),
),
child: const Icon(Icons.cameraswitch),
),
ElevatedButton(
onPressed: () => onMicButtonTap.call(audioEnabled),
style: ButtonStyle(
shape: MaterialStateProperty.all<CircleBorder>(
const CircleBorder()),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.all(10.0),
),
elevation: MaterialStateProperty.all<double>(8.0),
),
child:audioEnabled
? const Icon(Icons.mic)
: const Icon(Icons.mic_off),
),
ElevatedButton(
onPressed: () => videoControl.call(videoEnabled),
style: ButtonStyle(
shape: MaterialStateProperty.all<CircleBorder>(
const CircleBorder()),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.all(10.0),
),
elevation: MaterialStateProperty.all<double>(8.0),
),
child: videoEnabled
? const Icon(Icons.videocam)
: const Icon(Icons.videocam_off),
),

ElevatedButton(
onPressed: onFullScreenButtonTap,
style: ButtonStyle(
shape: MaterialStateProperty.all<CircleBorder>(
const CircleBorder()),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
const EdgeInsets.all(10.0),
),
elevation: MaterialStateProperty.all<double>(8.0),
),
child: const Icon(Icons.fullscreen),
),
],
),
),
),
],
),
);
}
}